#include "brushutilities.h"



BrushUtilities::BrushUtilities()
{
    size = 10;
    breadth = 1;
    height = 16;
    shape = SPHERE;
    axesToFix = NO_AXIS;
    customShapes = vector<pair<string, set<vector3<int> > > >();
    global=GlobalStates::getInstance();
    dist=256;
    mouseMove = false;
}

BrushUtilities * BrushUtilities::getInstance()
{
    static BrushUtilities* inst=0;
    if(!inst)inst = new BrushUtilities();
    inst->global = GlobalStates::getInstance();
    inst->mouseMove = false;
    return inst;
}


Result BrushUtilities::setSize(int a)
{
    if (a < WORLD_YSIZE && a>0) {
        size = a;
        return Result(true, "Brush size set. ");
    }
    return Result(false, "Invalid brush size; please key in a value between 0 and 128.");
}

Result BrushUtilities::setSize(int x, int y, int z)
{
    if (x < WORLD_XSIZE && y<WORLD_YSIZE && z<WORLD_ZSIZE && x>0 && y>0 && z>0) {
        size = x;
        breadth = z;
        height = y;
        return Result(true, "Brush size set.");
    }

    return Result(false, "Invalid brush size, please key in a value between 0 and 128.");
}


Result BrushUtilities::setShape(int a)
{
    if (a <= COUNT && a > 0) {
        shape = a;
        return Result(true, "Brush shape set");
    } else if (a <= customShapes.size() + COUNT && a>0) {
        shape = a;
        return Result(true, "Brush shape set");
    } else {
        return Result(false, "Invalid brush shape");
    }
}

Result BrushUtilities::setBreadth(int a) {
    if (a<WORLD_ZSIZE){
        breadth = a;
        return Result(true, "Breadth set. ");
    }
    return Result(false, "Invalid breadth, please key in a value between 0 and 236.");
}

Result BrushUtilities::setHeight(int a) {
    if (a<WORLD_YSIZE){
        height = a;
        return Result(true, "Height set.");
    }
    return Result(false, "Height has be between 0 and 128");
}

Result BrushUtilities::setAxis(FIXED_AXES axis, bool toFix){
    if (toFix) {
        axesToFix |= axis;
        return Result(true, "axis fixed");
    }
    else {
        axesToFix &= ~axis;
        return Result(true, "axis removed");
    }
}



bool BrushUtilities::isAxisFixed(FIXED_AXES axis){
    return axesToFix & axis;
}

string BrushUtilities::getShapeName(int identifier){
    int i = identifier - COUNT - 1;
    if (i < customShapes.size() && i >= 0){
        return customShapes[i].first;
    }
    else return "";
}



Result BrushUtilities::setShapeName(int identifier, string n){
    int i = identifier - COUNT - 1;
    if (i >= customShapes.size() || i < 0)
        return Result(false, "No such shape!");
    else if (n==""){
        return Result(false, "Invalid name");
    }
    else {
        customShapes[i].first = n;
        string s = "Shape name has been set to ";
        s = s.append(n);
        return Result (true, s);
    }
}

Result BrushUtilities::deleteShape(int identifier){
    int i = identifier - COUNT - 1;
    if (i>=0 && i < customShapes.size()) {
        string s = customShapes[i].first;
        customShapes.erase(customShapes.begin() + i);
        s = s.append(" has been deleted");
        return Result(true, s);
    }
    else {
        return Result (false, "No such custom shape");
    }
}

Result BrushUtilities::addShape(){
    if (global->isSelectionEmpty())
        return Result(false, "No voxels in selection");
    string n = "Shape ";
    ostringstream convert;
    convert << customShapes.size() + 1;
    n = n.append(convert.str());
    customShapes.push_back(pair<string, set<vector3<int> > >(n, global->getSelection()));
    return Result(true, "Custom shape added");
}

Result BrushUtilities::addShape(string n){
    if (global->isSelectionEmpty())
        return Result(false, "No voxels in selection");
    customShapes.push_back(pair<string, set<vector3<int> > >(n, global->getSelection()));
    return Result(true, "Custom shape added");
}

Result BrushUtilities::execute(MODE m)
{
    Result res(true, "Voxels have been added");

    colour col = global->getRGB();
    int x = coords.x;
    int y = coords.y;
    int z = coords.z;
    voxel v;
    int s = size/2;

    v.setColor(col);

    /*=========================CUBE============================*/
    if (shape==CUBE) {
        for(int i = x - s; i<x+(size-s); i++)
            for (int j = y - s; j<y+(size - s); j++){
                for (int k = z; k<z+size; k++) {
                    if (m == ADD)
                        REND->addVoxel(i, j, k, v);
                    else if (m==ERASE)
                        REND->removeVoxel(i, j, k);
                }
            }
        if (m==DISPLAY_ADD){
            Drawing::drawWireCuboid(coords-vector3<int>(s,s,0),
                                    coords+vector3<int>(size-s,size-s,size),
                                    ADD_R, ADD_G, ADD_B);
        }
        else if (m==DISPLAY_ERASE){
            Drawing::drawWireCuboid(coords-vector3<int>(s,s,0),
                                    coords+vector3<int>(s,s,size),
                                    ERASE_R, ERASE_G, ERASE_B);
        }
        if (m==ERASE) res.setMessage("Voxels have been erased");
        return res;
    }

    /*=========================CUBOID============================*/
    else if (shape==CUBOID) {
        int h = height/2;
        for (int i = x - s; i<x+(size-s); i++)
            for (int j = y-h; j < y+(height-h); j++)
                for (int k = z; k<z+breadth; k++) {
                    if (m == ADD)
                        REND->addVoxel(i, j, k, v);
                    else if (m==ERASE)
                        REND->removeVoxel(i, j, k);
                }
        if (m==DISPLAY_ADD){
            Drawing::drawWireCuboid(vector3<int>(x-s, y-h, z),
                                    vector3<int>(x+size-s, y+(height - h), z+breadth),
                                    ADD_R, ADD_G, ADD_B);
        }
        else if (m==DISPLAY_ERASE) {
            Drawing::drawWireCuboid(vector3<int>(x-s, y-height+h, z),
                                    vector3<int>(x+size-s, y+h, z+breadth),
                                    ERASE_R, ERASE_G, ERASE_B);
        }
        if (m==ERASE) res.setMessage("Voxels have been erased");
        return res;
    }


    /*=========================SPHERE============================*/
    else if (shape==SPHERE) {
        for (int i = x-size; i<x+size; i++)
            for (int j = y-size; j<y+size; j++)
                for (int k = z-size; k<z+size; k++) {
                    vector3<int> currentCoords (i, j, k);
                    if ((currentCoords - coords).mag()<size) {
                        if (m == ADD)
                            REND->addVoxel(i, j, k, v);
                        else if (m==ERASE)
                            REND->removeVoxel(i, j,k);
                    }
                }
        if (m==DISPLAY_ADD){
            glPushMatrix();
            glTranslatef(x, y, z);
            Drawing::drawSphere(size, 10,
                                ADD_R, ADD_G, ADD_B,
                                true);
            glPopMatrix();
        }
        else if (m==DISPLAY_ERASE){
            glPushMatrix();
            glTranslatef(x, y, z);
            Drawing::drawSphere(size, 10,
                                ERASE_R, ERASE_G, ERASE_B,
                                true);
            glPopMatrix();

        }
        if (m==ERASE) res.setMessage("Voxels have been erased");
        return res;
    }


    /*=========================PYRAMID============================*/
    else if (shape==PYRAMID) {
        float gradient = ((float) size)/height;
        float r = size;
        for (int j = y; j<y+height && r>0; j++)  {
            int currentRadius = r;
            if ((r - floor(r) >=0.5))
                currentRadius = ceil(r);
            else currentRadius = floor(r);
            for (int i = x-currentRadius; i<x+currentRadius; i++)
                for (int k = z-currentRadius; k<z+currentRadius; k++) {
                    if (m == ADD)
                        REND->addVoxel(i, j, k, v);
                    else if (m==ERASE)
                        REND->removeVoxel(i, j, k);
                }
            r-=gradient;
        }
        if (m==DISPLAY_ADD){
            glPushMatrix();
            glTranslatef(x-size,y,z-size);
            Drawing::drawPyramid(size*2, height,
                                 ADD_R, ADD_G, ADD_B,
                                 true);
            glPopMatrix();
        }
        else if (m==DISPLAY_ERASE){
            glPushMatrix();
            glTranslatef(x-size,y,z-size);
            Drawing::drawPyramid(size*2, height,
                                 ERASE_R, ERASE_G, ERASE_B,
                                 true);
            glPopMatrix();
        }
        if (m==ERASE) res.setMessage("Voxels have been erased");
        return res;
    }


    /*=========================CUSTOM============================*/
    else if (shape > COUNT && shape <= customShapes.size() + COUNT){
        set<vector3<int> >::iterator it;
        set<vector3<int> > customS = customShapes[shape - COUNT -1].second;
        int maxX=0, maxY=0, maxZ=0, minX=WORLD_XSIZE, minY=WORLD_YSIZE, minZ = WORLD_XSIZE;


        for(it = customS.begin();
            it!=customS.end();
            it++){
            vector3<int> hi = *it;
            maxX = MAX(maxX, hi.x);
            maxY = MAX(maxY, hi.y);
            maxZ = MAX(maxZ, hi.z);
            minX = MIN(minX, hi.x);
            minY = MIN(minY, hi.y);
            minZ = MIN(minZ, hi.z);
        }
        vector3<int> mid = vector3<int>((maxX+minX)/2, (maxY+minY)/2, (maxZ+minZ)/2);
        for(it = customS.begin();
            it!=customS.end();
            it++){
            vector3<int> hi = *it;
            if (m==ADD)
                REND->addVoxel(hi.x+coords.x - mid.x,
                               hi.y + coords.y - mid.y,
                               hi.z + coords.z - mid.z,
                               v);
            else if (m==ERASE)
                REND->removeVoxel(hi.x+coords.x - mid.x,
                                  hi.y + coords.y - mid.y,
                                  hi.z + coords.z - mid.z);
        }
        if (m==DISPLAY_ADD) {
            Drawing::drawWireCuboid(vector3<int>(coords.x - mid.x + minX,
                                                 coords.y - mid.y + minY,
                                                 coords.z - mid.z + minZ),
                                    vector3<int>(coords.x - mid.x + maxX,
                                                 coords.y - mid.y + maxY,
                                                 coords.z - mid.z + maxZ),
                                    ADD_R, ADD_G, ADD_B);
        }
        else if (m==DISPLAY_ERASE){
            Drawing::drawWireCuboid(vector3<int>(coords.x - mid.x + minX,
                                                 coords.y - mid.y + minY,
                                                 coords.z - mid.z + minZ),
                                    vector3<int>(coords.x - mid.x + maxX,
                                                 coords.y - mid.y + maxY,
                                                 coords.z - mid.z + maxZ),
                                    ERASE_R, ERASE_G, ERASE_B);
        }
        if (m==ERASE) res.setMessage("Voxels have been erased");
        return res;
    }
    else return Result(!IS_SUCCESSFUL, "Voxels have not been added");
}





Result BrushUtilities::keyPressEvent(QKeyEvent *event){
    int key = event->key();
    switch(key){
    case(KEY_FIX_X):
        setAxis(XAXIS, true);
//        break;
        return Result(true, "X Axis is fixed");
    case (KEY_FIX_Y):
        setAxis(YAXIS, true);
//        break;
        return Result(true, "Y Axis is fixed");
    case (KEY_FIX_Z):
        setAxis(ZAXIS, true);
//        break;
        return Result(true, "Z Axis is fixed");
    }
    return Result(false, "Unrecognised key");
}

Result BrushUtilities::keyReleaseEvent(QKeyEvent *event) {
    int key = event->key();
    if (!event->isAutoRepeat()) {
        switch(key){
        case(KEY_FIX_X):
            setAxis(XAXIS, false);
//            break;
            return Result(true, "X Axis is unfixed");
        case (KEY_FIX_Y):
            setAxis(YAXIS, false);
//            break;
            return Result(true, "Y Axis is unfixed");
        case (KEY_FIX_Z):
            setAxis(ZAXIS, false);
//            break;
            return Result(true, "Z Axis is unfixed");
        }
        return Result(false, "Unrecognised key");
    }
    return Result(false, "Repeated key");
}


Result BrushUtilities::mouseEnterEvent(QEvent *event){
    mouseMove = true;
    return Result(true);
}

Result BrushUtilities::mouseLeaveEvent(QEvent *event){
    mouseMove = false;
    return Result(true);
}

Result BrushUtilities::mouseMoveEvent(QMouseEvent *event){
    vector3<int> mouseCoords
            = event->modifiers()&KEY_FIX_PLANE?
                REND->mouseUtils.getMouseOverVoxelPt(event->x(),event->y(),dist):
                REND->mouseUtils.getMouseOverVoxelPt(event->x(), event->y());

    if(!isAxisFixed(XAXIS)) {
        if (!GlobalStates::isInAcceptableRange(XAXIS, mouseCoords.x))
            return Result(false, "Out of world");
        coords.x = mouseCoords.x;
    }
    if (!isAxisFixed(YAXIS)){
        if (!GlobalStates::isInAcceptableRange(YAXIS, mouseCoords.y))
            return Result(false, "Out of world");
        coords.y = mouseCoords.y;
    }
    if (!isAxisFixed(ZAXIS)){
        if (!GlobalStates::isInAcceptableRange(ZAXIS, mouseCoords.z))
            return Result(false, "Out of world");
        coords.z = mouseCoords.z;
    }

    return Result(true);
}

Result BrushUtilities::mousePressEvent(QMouseEvent *event){
    vector3<int> mouseCoords = REND->mouseUtils.getMouseOverVoxelPt(event->x(), event->y());
    dist = (REND->Camera.getEyeVector() - coords.cast<float>()).mag();
    if(event->buttons()&Qt::LeftButton){
        if(!isAxisFixed(XAXIS)) {
            if (!GlobalStates::isInAcceptableRange(XAXIS, mouseCoords.x))
                return Result(false, "Out of world");
            coords.x = mouseCoords.x;
        }
        if (!isAxisFixed(YAXIS)){
            if (!GlobalStates::isInAcceptableRange(YAXIS, mouseCoords.y))
                return Result(false, "Out of world");
            coords.y = mouseCoords.y;
        }
        if (!isAxisFixed(ZAXIS)){
            if (!GlobalStates::isInAcceptableRange(ZAXIS, mouseCoords.z))
                return Result(false, "Out of world");
            coords.z = mouseCoords.z;
        }
    }

    return Result(true);
}
